
package com.SPOooON.googletaskssync;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.api.client.extensions.android2.AndroidHttp;
import com.google.api.client.googleapis.auth.oauth2.draft10.GoogleAccessProtectedResource;
import com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.json.JsonHttpRequest;
import com.google.api.client.http.json.JsonHttpRequestInitializer;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.client.util.DateTime;
import com.google.api.services.tasks.Tasks;
import com.google.api.services.tasks.Tasks.TasksOperations.Patch;
import com.google.api.services.tasks.Tasks.TasksOperations.Update;
import com.google.api.services.tasks.TasksRequest;
import com.google.api.services.tasks.model.Task;
import com.google.api.services.tasks.model.TaskList;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.content.Context;
import android.content.SyncResult;
import android.database.Cursor;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;

@SuppressWarnings("deprecation")
public class GoogleTasksSyncAdapter extends AbstractThreadedSyncAdapter {

	private static final String TAG = "GoogleTasksSyncAdapter";
	private static final String AUTH_TOKEN_TYPE = "Manage your tasks";
	
	private Context context;
	private ContentProviderClient provider;
	private Account account;
	private long timeStamp = 0L;
	private SyncResult syncResult;
	private String token;
	
	private final HashMap<String, TaskListHelper> taskLists = new HashMap<String, TaskListHelper>();
	private final HashMap<String, TaskHelper> tasks = new HashMap<String, TaskHelper>();
	
	private GoogleAccessProtectedResource accessProtectedResource;
	private Tasks tasksService;
	private HttpTransport transport;
	private GoogleAccountManager manager;

	public GoogleTasksSyncAdapter(Context context){
		super(context, true);
		this.context = context;
		
		Logger.getLogger("com.google.api.client").setLevel(Level.ALL);
	}

	@Override
	public void onPerformSync(Account account
			, Bundle extras
			, String authority
			, ContentProviderClient provider
			, SyncResult syncResult) {
		
		this.provider = provider; 
		this.account = account; 
		this.syncResult = syncResult;
		
		Log.d(TAG, "Starting sync");
		
		try {
			manager = new GoogleAccountManager(context);
			
			token = manager.manager.blockingGetAuthToken(account, AUTH_TOKEN_TYPE, true);
			
			accessProtectedResource = new GoogleAccessProtectedResource(token);
			transport = AndroidHttp.newCompatibleTransport();
			
			tasksService = Tasks.builder(transport, new JacksonFactory())
				.setApplicationName(context.getString(R.string.google_tasks_sync_label))
				.setHttpRequestInitializer(accessProtectedResource)
				.setJsonHttpRequestInitializer(new GTJsonHttpRequestInitializer())
				.build();
			
			tasksService.tasklists().get("@default").execute();
			
			onTasksServiceAcquired();
			
		} catch (OperationCanceledException e) {
			syncResult.stats.numAuthExceptions++;
		} catch (AuthenticatorException e) {
			syncResult.stats.numAuthExceptions++;
		} catch (IOException e) {						
			syncResult.stats.numIoExceptions++;
			
			e.printStackTrace();
			
			GoogleJsonResponseException exception = (GoogleJsonResponseException) e;
			if (exception != null) {
				if (exception.getStatusCode() == 401) {
					syncResult.stats.numIoExceptions--;					
					manager.invalidateAuthToken(token);
					syncResult.fullSyncRequested = true;
				}
			}
		} finally {
			Log.d(TAG, syncResult.toDebugString());
		}
		
		Log.d(TAG, "Stopping sync " + syncResult.toDebugString() );
	}
	
	public void onTasksServiceAcquired() throws IOException {
		
		// step 1 iterate through tasklists .. 
		Cursor c = null;
		try {
			
			//load local tasklists
			c = provider.query(GoogleTasksContentProvider.TaskLists.CONTENT_URI
					, null
					, GoogleTasksContentProvider.TaskLists.Columns.UPDATED + " > ?"
					, new String[] { Long.toString(timeStamp) }
					, null);
			
			if (c.moveToFirst()) {
				do {
					String id = c.getString(c.getColumnIndex(GoogleTasksContentProvider.TaskLists.Columns.ID));
					getTaskListHelper(id).local = new TaskListWrapper(c);
				} while (c.moveToNext());
			}
			
			c.close();
			
			// load cloud tasklists
			java.util.List<TaskList> tls = tasksService.tasklists().list().execute().getItems();
			
			for (TaskList tl : tls) {
				getTaskListHelper(tl.getId()).cloud = new TaskListWrapper(account.name, tl);
			}
			
			// attempt to load tasklists that fall outside of timestamp parameter for comparison.
			for (TaskListHelper tlh : taskLists.values()) {
				if (tlh.cloud == null & tlh.local == null) continue;
								
				if (tlh.cloud ==  null && !tlh.local.getNew()) {
					try {
						TaskList taskList = tasksService.tasklists().get(tlh.local.getTaskList().getId()).execute();
						tlh.cloud = new TaskListWrapper(account.name, taskList);
						
					} catch (IOException e) {
						GoogleJsonResponseException exception = (GoogleJsonResponseException) e;
						if (exception != null) {
							if (!(exception.getStatusCode() == 404)) {
								throw e;
							}
						}						
					}
				}
				
				if (tlh.local == null) {
					c = provider.query(GoogleTasksContentProvider.TaskLists.CONTENT_URI
							, null
							, GoogleTasksContentProvider.TaskLists.Columns.ID + " = ?"
							, new String[] { tlh.cloud.getTaskList().getId() }
							, null);
					
					if (c.moveToFirst()) {
						tlh.local = new TaskListWrapper(c);
					}
					
					c.close();
				}
			}			
		} catch (RemoteException e) {
			e.printStackTrace();
			syncResult.stats.numSkippedEntries++;
		} finally {
			
			if (c != null) 
				c.close();
		}
		
		for (TaskListHelper tlh : taskLists.values()) {
			syncResult.stats.numEntries++;
			tlh.compare(tasksService, provider, account, syncResult);
		}
		
		// step 2, iterate through tasks		
		try {
			
			c = provider.query(GoogleTasksContentProvider.TaskLists.CONTENT_URI
					, new String[]{ GoogleTasksContentProvider.TaskLists.Columns._ID }
					, GoogleTasksContentProvider.TaskLists.Columns.NEW + " = 0"
					, null
					, null);
			
			ArrayList<Integer> tls = new ArrayList<Integer>();
			
			if (c.moveToFirst()) {
				do {
					tls.add(c.getInt(c.getColumnIndex(GoogleTasksContentProvider.TaskLists.Columns._ID)));
				} while (c.moveToNext());
			}
			
			c.close();
			
			for (int taskList : tls) {
				c = provider.query(GoogleTasksContentProvider.Tasks.CONTENT_URI
						, null
						, GoogleTasksContentProvider.Tasks.Columns.TASKLIST + " = ? AND " + GoogleTasksContentProvider.Tasks.Columns.UPDATED + " > ?"
						, new String[] { Integer.toString(taskList), Long.toString(timeStamp) }
						, null);
				
				if (c.moveToFirst()) {
					do {

						String id = c.getString(c.getColumnIndex(GoogleTasksContentProvider.Tasks.Columns.ID));
						getTaskHelper(id).local = new TaskWrapper(c);						
					} while (c.moveToNext());
				}
				
				c.close();
				
				String id = getTaskListId(provider, taskList);				
				com.google.api.services.tasks.Tasks.TasksOperations.List list = tasksService.tasks().list(id);
				list.setShowDeleted(true);
				list.setShowCompleted(true);
				list.setShowHidden(true);
				
				if (timeStamp > 0) 
					list.setUpdatedMin(new DateTime(timeStamp).toStringRfc3339());
								
				java.util.List<Task> ts = list.execute().getItems();
				if (ts != null) {
					for (Task task : ts) {
						if (task != null) {
							getTaskHelper(task.getId()).cloud = new TaskWrapper(account.name, taskList, task);						
						}
					}
				}
				
				// attempt to load tasklists that fall outside of timestamp parameter for comparison.
				for (TaskHelper th : tasks.values()) {
					if (th.cloud == null & th.local == null) continue;
									
					if (th.cloud ==  null && !th.local.getNew()) {
						try {
							Task task = tasksService.tasks().get(getTaskListId(provider, th.local.getTaskList()), th.local.getTask().getId()).execute();
							th.cloud = new TaskWrapper(account.name, th.local.getTaskList(), task);														
						} catch (IOException e) {
							GoogleJsonResponseException exception = (GoogleJsonResponseException) e;
							if (exception != null) {
								if (!(exception.getStatusCode() == 404)) {
									throw e;
								}
							}						
						}
					}
					
					if (th.local == null) {
						c = provider.query(GoogleTasksContentProvider.Tasks.CONTENT_URI
								, null
								, GoogleTasksContentProvider.Tasks.Columns.ID + " = ?"
								, new String[] { th.cloud.getTask().getId() }
								, null);
						
						if (c.moveToFirst()) {
							th.local = new TaskWrapper(c);
						}
						
						c.close();
					}
				}			
				
			}
			
		} catch (RemoteException e) {
			e.printStackTrace();
			syncResult.stats.numSkippedEntries++;
		} finally {
			
			if (c != null)
				c.close();
		}
		
		String previous = null;
		for (TaskHelper th : tasks.values()) {
			syncResult.stats.numEntries++;
			th.compare(tasksService, provider, account, syncResult);
		}
		
		provider.release();
	}
	
	private TaskListHelper getTaskListHelper(String id) {
		TaskListHelper h = taskLists.get(id);
		
		if (h==null) {
			h = new TaskListHelper();
			taskLists.put(id, h);
		}
		
		return h;
	}
	
	private TaskHelper getTaskHelper(String id) {
		TaskHelper h = tasks.get(id);
		
		if (h==null) {
			h = new TaskHelper();
			tasks.put(id, h);
		}
		
		return h;
	}
	
	private static String getTaskListId(ContentProviderClient provider, int _id) throws RemoteException {
		Cursor c = provider.query(GoogleTasksContentProvider.TaskLists.CONTENT_URI
				, new String[]{ GoogleTasksContentProvider.TaskLists.Columns.ID }
				, GoogleTasksContentProvider.TaskLists.Columns._ID + " = ?"
				, new String[]{ Integer.toString(_id) }
				, null);
		
		if (c.moveToFirst()) {
			return c.getString(0);
		}		
		
		return null;
	}
	
	private static class TaskListHelper {
		private static final String TAG = "TaskListHelper";
		public TaskListWrapper cloud;
		public TaskListWrapper local;
		
		public void compare(Tasks tasksService, ContentProviderClient provider, Account account, SyncResult syncResult) throws IOException {
			
			// do nothing
			if (local == null && cloud == null) {
				Log.d(TAG, "do nothing");
				return;
			}
			
			try {
				
				// download and insert
				if (local == null) {
					
					provider.insert(GoogleTasksContentProvider.TaskLists.CONTENT_URI, cloud.getContentValues());
					syncResult.stats.numInserts++;
					return;
				}
				
				// (upload and insert) or delete
				if (cloud == null) {
					
					// tasklist has been deleted, remove it from db
					// remove child tasks as well
					// and stop processing
					if (!local.getNew()) {
						
						provider.delete(GoogleTasksContentProvider.TaskLists.CONTENT_URI
								, GoogleTasksContentProvider.TaskLists.Columns.ID + " = ?"
								, new String[]{ local.getTaskList().getId() });
						
						provider.delete(GoogleTasksContentProvider.Tasks.CONTENT_URI
								, GoogleTasksContentProvider.Tasks.Columns.TASKLIST + " = ?"
								, new String[]{ local.getTaskList().getId() });
						
						syncResult.stats.numDeletes++;
						return;
					} else {
						
						// insert into cloud
						TaskList taskList = tasksService.tasklists().insert(local.getTaskList()).execute();
						cloud = new TaskListWrapper(account.name, taskList);
						cloud.setNew(false);
						syncResult.stats.numInserts++;
					}
					
				}
				
				long localTimeStamp = local.getTaskList().getUpdated().getValue();
				long cloudTimeStamp = cloud.getTaskList().getUpdated().getValue();
				
				// upload and overwrite
				if (localTimeStamp > cloudTimeStamp) {
					TaskList taskList = tasksService.tasklists().update(local.getTaskList().getId(), local.getTaskList()).execute();
					cloud = new TaskListWrapper(account.name, taskList);
					syncResult.stats.numUpdates++;
				} 
			
				// download and overwrite
				// either cloud is newer and needs to be written to db
				// or upload return value needs to be written to db				
				provider.update(GoogleTasksContentProvider.TaskLists.CONTENT_URI
						, cloud.getContentValues()
						, GoogleTasksContentProvider.TaskLists.Columns._ID + " = ?"
						, new String[]{ Integer.toString(local.get_id()) });
				
				syncResult.stats.numUpdates++;
				
				// id changed, need to roll out to tasks table
				if (local.getTaskList().getId() ==null || !local.getTaskList().getId().equals(cloud.getTaskList().getId())) {
					
					ContentValues cv = new ContentValues();
					cv.put(GoogleTasksContentProvider.Tasks.Columns.TASKLIST, cloud.getTaskList().getId());
					
					provider.update(GoogleTasksContentProvider.Tasks.CONTENT_URI
							, cv
							, GoogleTasksContentProvider.Tasks.Columns.TASKLIST + " = ?"
							, new String[]{ Integer.toString(local.get_id()) });
				}				
			} catch (RemoteException e) {
				e.printStackTrace();
				syncResult.stats.numSkippedEntries++;
			}
		}
	}	
	
	private static class TaskHelper {
		
		private static final String TAG = "TaskHelper";
		public TaskWrapper local;
		public TaskWrapper cloud;
		
		public void compare(Tasks tasksService, ContentProviderClient provider, Account account, SyncResult syncResult) throws IOException {
			
			// do nothing
			if (local == null && cloud == null) {
				syncResult.stats.numSkippedEntries++;
				return;
			}			
			
			try {
				
				// new task, download and insert into local db
				if (local == null) {					
					provider.insert(GoogleTasksContentProvider.Tasks.CONTENT_URI, cloud.getContentValues());
					local = new TaskWrapper(account.name, cloud.getTaskList(), cloud.getTask());
					syncResult.stats.numInserts++;
					return;
				}
				
				// upload and insert, or delete
				if (cloud == null) {
					
					// task has been deleted from cloud, delete from local db
					if (!local.getNew()) {
						
						syncResult.stats.numDeletes++;
						
						provider.delete(GoogleTasksContentProvider.Tasks.CONTENT_URI
								, GoogleTasksContentProvider.Tasks.Columns.ID + " = ?"
								, new String[]{ local.getTask().getId() });
						local = null;
					} else {
						
						//new task, upload to cloud
						Cursor c = provider.query(GoogleTasksContentProvider.Tasks.CONTENT_URI
								, new String[] { GoogleTasksContentProvider.Tasks.Columns.ID }
								, GoogleTasksContentProvider.Tasks.Columns._ID + " = ? "
								, new String[] { Integer.toString(local.getPrevious()) }
								, null );
						
						String previous = null;
												
						if (c.moveToFirst()) {
							previous = c.getString(c.getColumnIndex(GoogleTasksContentProvider.Tasks.Columns.ID));
						}
						
						Task task = tasksService.tasks().insert(getTaskListId(provider, local.getTaskList()), local.getTask()).setPrevious(previous).execute();
						cloud = new TaskWrapper(account.name, local.getTaskList(), task);
						syncResult.stats.numInserts++;
					}
				}
				
				if (cloud == null || local == null)
					return;
								
				long localTimeStamp = local.getTask().getUpdated().getValue();
				long cloudTimeStamp = cloud.getTask().getUpdated().getValue();
				
				// upload and overwrite
				if (localTimeStamp > cloudTimeStamp) {
					
					Log.d(TAG, "uploading " + local.getTask().getTitle());
					Task task = tasksService.tasks().update(getTaskListId(provider, local.getTaskList())
							, local.getTask().getId()
							, local.getTask()).execute();
					
					cloud = new TaskWrapper(account.name, local.getTaskList(), task);
					syncResult.stats.numUpdates++;
				} 
			
				// download and overwrite
				// either cloud is newer and needs to be written to db
				// or upload return value needs to be written to db				
				provider.update(GoogleTasksContentProvider.Tasks.CONTENT_URI
						, cloud.getContentValues()
						, GoogleTasksContentProvider.Tasks.Columns._ID + " = ?"
						, new String[]{ Integer.toString(local.get_id()) });
				
				syncResult.stats.numUpdates++;
				
			} catch (RemoteException e) {
				e.printStackTrace();
				syncResult.stats.numSkippedEntries++;
			}
		}
	}
	
	private class GTJsonHttpRequestInitializer implements JsonHttpRequestInitializer {

		@Override
		public void initialize(JsonHttpRequest request) throws IOException {
			
			TasksRequest tasksRequest = (TasksRequest) request;
			tasksRequest.setKey("AIzaSyCrP3_vyJQ53xy1dc5osGzlA47XglzkR48");
		}
	}	
}
